<?php
/**
 * @link https://ym2.cleverstone.top
 * @copyright Copyright (c) 2020 Yii Manager Software LLC
 */

namespace builder\form_step;

use builder\base\UndefinedParamsException;
use builder\form_step\util\fssr\FssrType;
use builder\form_step\util\step\SiType;
use builder\helper\App;
use builder\helper\H5;
use builder\helper\RegEx;
use builder\helper\Url;
use builder\base\Builder;
use builder\base\ViewBuilderInterface;
use yii\base\ErrorException;
use yii\base\InvalidArgumentException;
use yii\web\AssetBundle;
use Yii;
use yii\web\View;

/**
 * 分步表单构建器
 * @property bool $partial 是否是局部继承, 默认false
 * @property string $submitUrl 分步表单提交链接, 默认当前URL
 * @property string $title 标题
 * @property array $rootLayout 根布局
 * @property array $stepItem 分步内容项
 * @property-read array $footerBtn 分步表单底部按钮
 * @property-write true $submitBtn 显示分步表单提交按钮
 * @property-write true $closeBtn 显示弹窗关闭按钮
 * @property-write true $backBtn 显示页面返回按钮
 * @property-write array $extraBtn 设置额外的按钮
 * @property bool $autoClose 是否提交后自动关闭分步表单, 默认false
 * @property bool $autoBack 是否提交后自动返回, 默认false
 * @property array $submitConfirm 设置提交确认框
 * @property array $submittedCalls 分步表单提交后的回调, 空数组代表不回调
 * @property array $fssRedirect 分步表单提交后重定向, 空数组代表无操作
 * @property array $extraJs 设置额外的Js
 * @property array $extraCss 设置额外的Css
 * @property array $extraAssetBundle 设置额外的静态资源包
 * @author cleverstone
 * @since ym2.0
 */
class FormStepBuilder extends Builder implements ViewBuilderInterface
{
    /**
     * The location of registered html or text.
     * This means the location is in the form step head section.
     */
    const POS_HEAD = 1;

    /**
     * The location of registered html or text.
     * This means the location is at the end of the form step section.
     */
    const POS_END = 2;

    /**
     * @var int 最大步数
     */
    public $maxStep = 9;

    /**
     * @var array 分步默认图标集合
     */
    public $stepIconMap = [
        'bi bi-1-circle',
        'bi bi-2-circle',
        'bi bi-3-circle',
        'bi bi-4-circle',
        'bi bi-5-circle',
        'bi bi-6-circle',
        'bi bi-7-circle',
        'bi bi-8-circle',
        'bi bi-9-circle',
    ];

    /**
     * @var array 分步激活图标集合
     */
    public $stepIconActiveMap = [
        'bi bi-1-circle-fill',
        'bi bi-2-circle-fill',
        'bi bi-3-circle-fill',
        'bi bi-4-circle-fill',
        'bi bi-5-circle-fill',
        'bi bi-6-circle-fill',
        'bi bi-7-circle-fill',
        'bi bi-8-circle-fill',
        'bi bi-9-circle-fill',
    ];

    /**
     * @var string 分步完成图标
     */
    public $stepOkIcon = 'bi bi-check-circle';

    /**
     * @var bool 是否局部继承
     * @see $partial
     * @see setPartial()
     * @see getPartial()
     */
    private $_partial = false;

    /**
     * @var string 提交链接
     * @see $submitUrl
     * @see setSubmitUrl()
     * @see getSubmitUrl()
     */
    private $_submitUrl;

    /**
     * @var string 标题
     * @see $title
     * @see setTitle()
     * @see getTitle()
     */
    private $_title;

    /**
     * @var array 分步内容项
     * @see $stepItem
     * @see setStepItem()
     * @see getStepItem()
     */
    private $_stepItem = [];

    /**
     * @var array 设置根布局
     * @see $rootLayout
     * @see setRootLayout()
     * @see getRootLayout()
     */
    private $_rootLayout = [];

    /**
     * @var array 分步表单切点
     * @see registerPointcut()
     * @see beginForm()
     * @see endForm()
     */
    private $_pointcutBody = [];

    /**
     * @var array 底部按钮
     * @see $footerBtn
     */
    private $_footerBtn = [];

    /**
     * @var bool 是否提交后自动关闭分步表单, 默认true
     * @see $autoClose
     * @see setAutoClose()
     * @see getAutoClose()
     */
    private $_autoClose = false;

    /**
     * @var bool 是否提交后自动返回, 默认false
     * @see $autoBack
     * @see setAutoBack()
     * @see getAutoBack()
     */
    private $_autoBack = false;

    /**
     * @var array 提交确认框
     * @see $submitConfirm
     * @see setSubmitConfirm()
     * @see getSubmitConfirm()
     */
    private $_submitConfirm = [
        'show' => false,
        'title' => '',
        'content' => '是否确认提交?',
    ];

    /**
     * @var array 分步表单自动提交后的回调,空数组代表不回调
     * - key 回调窗口
     *   self: 当前窗口
     *   parent: 父级窗口
     *   parent.parent.*: 祖先窗口
     *
     * - value 分步表单提交后的回调ID(构建器ID或自定义回调方法名称)
     *   构建器ID: 指定回调;如:表格行操作和工具栏操作中的modal分步表单, 如果回调, 则需要传入该表格的ID
     *   自定义回调方法名称: 指定回调;window.ym2CbkExport对象中的调用方法名称
     *   value为空数组: 回调所有;代表回调window.ym2CbkExport中的所有方法
     *
     * @see $submittedCalls
     * @see setSubmittedCalls()
     * @see getSubmittedCalls()
     */
    private $_submittedCalls = [];

    /**
     * @var array 分步表单提交后重定向, 空数组代表无操作
     * ========= 公共参数 =========
     * - type string 类型,枚举,支持modal和page
     * - route string 路由
     * - params array 参数
     * ========= modal参数 =========
     * - width string modal宽,modal有效
     * - height string modal高,modal有效
     * - title string modal标题,modal有效
     * - closeBtn boolean modal底部关闭按钮,modal有效
     * ========= page参数 =========
     * - target string 跳转目标,page有效
     * - window string JS窗口对象,page有效
     *
     * @see $fssRedirect
     * @see setFssRedirect()
     * @see getFssRedirect()
     */
    private $_fssRedirect = [];

    /**
     * @var array 额外的JS
     * ```php
     * $form->extraCss = <<<JS
     * console.log("测试一下 [setExtraCss] 方法;")
     * JS;
     *
     * ```
     * @see $extraJs
     * @see setExtraJs()
     * @see getExtraJs()
     */
    private $_extraJs = [];

    /**
     * @var array 额外的CSS
     * ```php
     * $form->extraCss = <<<CSS
     * body{background-color: #fff;}
     * CSS;
     *
     * ```
     * @see $extraCss
     * @see setExtraCss()
     * @see getExtraCss()
     */
    private $_extraCss = [];

    /**
     * @var array 额外的静态资源包
     * ```php
     * $form->extraAssetBundle = MemberAsset::class;
     *
     * ```
     * @see $extraAssetBundle
     * @see setExtraAssetBundle()
     * @see getExtraAssetBundle()
     */
    private $_extraAssetBundle = [];

    /**
     * 实例化
     * @param array $config
     * @return FormStepBuilder
     */
    public static function instance($config = [])
    {
        return new static($config);
    }

    /**
     * 设置视图局部继承
     * @param bool $boolean
     * @return $this
     */
    public function setPartial($boolean = true)
    {
        $this->_partial = $boolean;
        return $this;
    }

    /**
     * @return bool
     */
    public function getPartial()
    {
        return $this->_partial;
    }

    /**
     * 设置分步表单提交URL
     * @param string|array $route
     * @see Url::to()
     * @return $this
     */
    public function setSubmitUrl($route)
    {
        $this->_submitUrl = Url::to($route);
        return $this;
    }

    /**
     * @return string
     */
    public function getSubmitUrl()
    {
        if (empty($this->_submitUrl)) {
            return Url::current([], '');
        }

        return $this->_submitUrl;
    }

    /**
     * 设置标题
     * @param string $title 标题
     * @return $this
     */
    public function setTitle($title)
    {
        $this->_title = (string)$title;
        return $this;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->_title;
    }

    /**
     * 设置分步内容
     * @param array|\Closure|SiType $stepItem
     * @return $this
     * @throws ErrorException
     * @throws UndefinedParamsException
     * @throws \ReflectionException
     */
    public function setStepItem($stepItem)
    {
        if (count($this->_stepItem) > $this->maxStep) {
            throw new ErrorException('The maximum step number is ' . $this->maxStep);
        }

        if ($stepItem instanceof \Closure) {
            $stepItem = $this->closureDi($stepItem, SiType::class);
        }

        if ($stepItem instanceof SiType) {
            $stepItem = $stepItem->_toArray();
        }

        if (!is_array($stepItem)) {
            throw new InvalidArgumentException('Expected parameter [$stepItem] is array, But the others are given. ');
        }

        $this->_stepItem[] = $stepItem;
        return $this;
    }

    /**
     * @return array
     */
    public function getStepItem()
    {
        return $this->_stepItem;
    }

    /**
     * 设置根布局
     * - class array|string
     * - style array|string
     * @param array $options
     * @return $this
     */
    public function setRootLayout(array $options)
    {
        if (!empty($options['class'])) {
            $this->formatUiClass($options['class']);
        } else {
            $options['class'] = '';
        }

        if (!empty($options['style'])) {
            $this->formatStyle($options['style']);
        } else {
            $options['style'] = '';
        }

        if (!empty($options['attribute'])) {
            $this->formatHtmlAttribute($options['attribute']);
        } else {
            $options['attribute'] = '';
        }

        $this->_rootLayout = $options;
        return $this;
    }

    /**
     * @return array
     */
    public function getRootLayout()
    {
        if (empty($this->_rootLayout)) {
            return [
                'style' => '',
                'class' => '',
                'attribute' => '',
            ];
        }

        return $this->_rootLayout;
    }

    /**
     * 注册分步表单切点
     * @param string $content 内容
     * @param int $pos 切点
     * @return $this
     */
    public function registerPointcut($content, $pos = self::POS_HEAD)
    {
        $this->_pointcutBody[$pos][] = (string)$content;
        return $this;
    }

    /**
     * 设置分步表单提交按钮
     * @param string $title
     * @return $this
     */
    public function setSubmitBtn($title = '立即提交')
    {
        $this->_footerBtn['submit'] = [
            'title' => $title,
            'content' => '',
        ];
        return $this;
    }

    /**
     * 设置弹窗关闭按钮
     * @param string $title
     * @return $this
     */
    public function setCloseBtn($title = '关闭')
    {
        $this->_footerBtn['close'] = [
            'title' => $title,
            'content' => '',
        ];
        return $this;
    }

    /**
     * 设置页面返回按钮
     * @param string $title
     * @return $this
     */
    public function setBackBtn($title = '返回')
    {
        $this->_footerBtn['back'] = [
            'title' => $title,
            'content' => '',
        ];
        return $this;
    }

    /**
     * 设置额外的按钮
     * @param array $extraBtn
     * @return $this
     */
    public function setExtraBtn(array $extraBtn)
    {
        foreach ($extraBtn as $item) {
            $this->_footerBtn[] = [
                'title' => '',
                'content' => (string)$item,
            ];
        }
        return $this;
    }

    /**
     * @return array
     */
    public function getFooterBtn()
    {
        return $this->_footerBtn;
    }

    /**
     * 分步表单自动提交后的回调, 空数组代表不回调
     * @param array $callback
     * @return $this
     */
    public function setSubmittedCalls(array $callback = ['parent' => null])
    {
        foreach ($callback as $window => &$item) {
            if (empty($item)) {
                $item = [];
            } else {
                $item = (array)$item;
            }
        }

        $this->_submittedCalls = $callback;
        return $this;
    }

    /**
     * @return array
     */
    public function getSubmittedCalls()
    {
        return $this->_submittedCalls;
    }

    /**
     * 设置提交后回调窗口
     * @param array|\Closure|FssrType $options
     * @return $this
     * @throws UndefinedParamsException
     * @throws \ReflectionException
     */
    public function setFssRedirect($options)
    {
        if ($options instanceof FssrType) {
            $options = $options->_toArray();
        } elseif ($options instanceof \Closure) {
            $options = $this->closureDi($options, FssrType::class);
            if (!is_array($options) && $options instanceof FssrType) {
                $options = $options->_toArray();
            }
        }

        if (!is_array($options)) {
            throw new InvalidArgumentException('Expected parameter [options] is array, But the others are given. ');
        }

        $this->_fssRedirect = $options;
        return $this;
    }

    /**
     * @return array
     */
    public function getFssRedirect()
    {
        return $this->_fssRedirect;
    }

    /**
     * 是否提交后自动关闭分步表单, 默认true
     * @param bool $boolean
     * @return $this
     */
    public function setAutoClose($boolean = true)
    {
        $this->_autoClose = $boolean;
        return $this;
    }

    /**
     * @return bool
     */
    public function getAutoClose()
    {
        return $this->_autoClose;
    }

    /**
     * 是否提交后自动返回, 默认false
     * @param bool $boolean
     * @return $this
     */
    public function setAutoBack($boolean = true)
    {
        $this->_autoBack = $boolean;
        return $this;
    }

    /**
     * @return bool
     */
    public function getAutoBack()
    {
        return $this->_autoBack;
    }

    /**
     * 设置分步表单提交确认框
     * @param string $title 提示标题
     * @param string $content 提示内容
     * @return $this
     */
    public function setSubmitConfirm($title = '', $content = '是否确认提交?')
    {
        $this->_submitConfirm['show'] = true;
        $this->_submitConfirm['title'] = $title;
        $this->_submitConfirm['content'] = $content;

        return $this;
    }

    /**
     * @return array
     */
    public function getSubmitConfirm()
    {
        return $this->_submitConfirm;
    }

    /**
     * 设置额外的JS
     * @param array|string $js 额外的JS脚本
     * @return $this
     */
    public function setExtraJs($js)
    {
        $js = (array)$js;
        foreach ($js as &$value) {
            if (is_callable($value)) {
                $value = call_user_func($value);
            }
        }

        $this->_extraJs = array_merge($this->_extraJs, $js);
        return $this;
    }

    /**
     * 获取额外的JS
     * @return array
     */
    public function getExtraJs()
    {
        return $this->_extraJs;
    }

    /**
     * 设置额外的CSS
     * @param array|string $css 额外的CSS脚本
     * @return $this
     */
    public function setExtraCss($css)
    {
        $css = (array)$css;
        foreach ($css as &$value) {
            if (is_callable($value)) {
                $value = call_user_func($value);
            }
        }

        $this->_extraCss = array_merge($this->_extraCss, $css);
        return $this;
    }

    /**
     * 获取额外的CSS
     * @return array
     */
    public function getExtraCss()
    {
        return $this->_extraCss;
    }

    /**
     * 设置额外的静态资源包
     * @param string|array $extraAssetBundle 静态资源包
     * @return $this
     */
    public function setExtraAssetBundle($extraAssetBundle)
    {
        $extraAssetBundle = (array)$extraAssetBundle;

        // 检查
        foreach ($extraAssetBundle as $assetBundle) {
            $tempObj = new $assetBundle;
            if (!$tempObj instanceof AssetBundle) {
                throw new InvalidArgumentException($assetBundle . ' is not instance of ' . AssetBundle::class);
            }
        }

        $this->_extraAssetBundle = array_merge($this->_extraAssetBundle, $extraAssetBundle);
        return $this;
    }

    /**
     * 获取额外的静态资源包
     * @return array
     */
    public function getExtraAssetBundle()
    {
        return $this->_extraAssetBundle;
    }

    /**
     * 开始分步表单
     * @return null|string
     */
    public function beginFormStep()
    {
        if (empty($this->_pointcutBody[self::POS_HEAD])) {
            return null;
        }

        return implode("\n", $this->_pointcutBody[self::POS_HEAD]);
    }

    /**
     * 结束分步表单
     * @return null|string
     */
    public function endFormStep()
    {
        if (empty($this->_pointcutBody[self::POS_END])) {
            return null;
        }

        return implode("\n", $this->_pointcutBody[self::POS_END]);
    }

    /**
     * 构建器名称
     * @return string
     */
    public function getName()
    {
        return 'form_step';
    }

    /**
     * @return string the view path that may be prefixed to a relative view name.
     */
    public function getViewPath()
    {
        return __DIR__;
    }

    /**
     * 分步表单渲染
     * @param boolean $onlyRenderContent 是否仅渲染内容
     * @return string
     * @throws \yii\base\InvalidConfigException
     */
    public function render($onlyRenderContent = false)
    {
        if ($this->title) {
            // 注册页面TITLE
            $this->_view->title = H5::encode(App::params('admin.brandName') . '|' . $this->title);
        }

        // 注册JS脚本
        $this->_view->registerJs($this->_resolveJsScript(), View::POS_END);

        // 注册额外CSS脚本
        if (!empty($this->extraCss)) {
            $this->_view->registerCss(implode("\n", $this->extraCss));
        }

        // 注册额外的AssetBundle
        if (!empty($this->extraAssetBundle)) {
            foreach ($this->extraAssetBundle as $assetBundle) {
                $this->_view->registerAssetBundle($assetBundle);
            }
        }

        // 渲染分步表单内容
        $content = $this->_view->render('view', [
            // view.php中参数注册
            'rootLayout' => $this->rootLayout,                                       // 根布局
            'footerBtn' => $this->footerBtn,                                         // 尾部按钮
            'stepItem' => $this->stepItem,                                           // 分步项

        ], $this);

        // 仅内容渲染
        if ($onlyRenderContent) {
            return $content;
        }

        // 局部布局渲染(不包含: 菜单和导航栏)
        if ($this->partial) {
            return $this->_view->renderFile($this->partialPath, ['content' => $content], $this);
        }

        // 主布局渲染(包含: 菜单和导航栏)
        return $this->_view->renderFile($this->mainPath, ['content' => $content], $this);
    }

    /**
     * 解析JS脚本
     * @return string
     */
    private function _resolveJsScript()
    {
        $jsScripts = $this->_view->render('app', [
            // app.php中参数注册
            'autoClose' => $this->autoClose,                                            // 是否提交后自动关闭分步表单
            'autoBack' => $this->autoBack,                                              // 是否提交后自动返回
            'submitConfirm' => $this->submitConfirm,                                    // 设置分步表单提交确认框
            'submittedCalls' => $this->submittedCalls,                                  // 分步表单自动提交后的回调, 空数组代表不回调
            'extraJs' => $this->extraJs,                                                // 额外的JS脚本
            'fssRedirect' => $this->fssRedirect,                                        // 分步表单提交后重定向, 空数组代表无操作

        ], $this);

        return RegEx::js($jsScripts);
    }
}